MySQL行级别由各个存储引擎实现,但是有些存储引擎不支持行级锁,例如MyISAM,所以只能使用表级锁。所以在并发控制的粒度上更粗一些。InnoDB是支持行级锁的,这也是InnoDB取代MyISAM的重要原因
两阶段锁
事务A | 事务B |
---|---|
begin;update t set k=k+1 where id=1;update t set k= k+1 where id = 2 | 没有 |
没有 | begin;update t set k=k+2 where id = 1 |
commit | 没有 |
- 事务A未执行完毕之前,事务B的操作会被锁住,事务A持有了id=1和id=2的行级锁。所以尽量把持有多个行的锁的事务操作放在最后,使其影响最小。
案例
- 顾客A余额扣除电影票价
- 影院B余额增加电影票价
- 记录一条交易日志
问题:如何调整顺序,使其影响最小
- 三个操作都是原子性操作,放在一个事务里面
- 另一个顾客买票时,有事务冲突的就是2,需要修改同一条记录数据。
- 按照3、1、2的顺序,语句2的锁时间最小,减少了事务之间的锁等待,提高了并发度
死锁与死锁监测
当并发系统中不同线程出现循环资源依赖。涉及的线程就回等待别的线程释放资源,就回导致几个线程进入无线等待的状态,从而导致死锁。
事务A | 事务B |
---|---|
begin;update t set k=k+1 where id =1 | begin; |
没有 | update t set k=k+1 where id = 2 |
update t set k = k+1 where id = 2 | 没有 |
没有 | update t set k=k+1 where id = 1 |
- 事务A在等待事务B中id=2的行锁
- 事务B在等待事务A中id=1的行锁
如何解决死锁问题
- 设置超时时间(innodb_lock_wait_timeout)
- 默认50s,第一个被锁住的线程在50s会超时退出。等待时间太长,对于在线服务不能容忍。
- 设置超时时间1s,如果只是简单的锁等待,容易造成误伤。
- 死锁检测
每当一个事务被所得时候,就要看它所依赖的线程有没有被人锁住,最后判断你是否出现了循环等待。
死锁监测方案:
- 如果所有事务都更新同一行记录,死锁监测的成本非常高,会导致性能问题。如1000个并发线程同时更新同一条记录,死锁监测的操作就是100万级别。这期间会消耗大量的CPU资源,效率很低。
- 关闭死锁监测,由业务上来保证不出现死锁。关掉死锁监测有一定的风险,当出现超时的时候,对业务是有损的。
- 控制并发度,使其死锁检测成本变低。当客户端很多的时候,及时每个客户端只有五个并发线程,峰值并发也可能达到3000
- 从设计上解决。将数据库的一行操作改成逻辑上的多行操作来减少锁冲突。如影院的余额设置在多条记录上,每次给应选加金额的时候,随机选一条来加,由此冲突概率将变成原来的1/10。